《Spring Boot+Vue全栈开发实战》读书笔记

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。——–王小波

写在前面

  • 嗯,回家处理一些事,所以离职了,之前的公司用开源技术封装了一套自己的低代码平台,所以之前学的spring Boot之类的东西都忘了很多,蹭回家的闲暇时间复习下。
  • 笔记整体以 Spring Boot+Vue全栈开发实战一书为方向,中间穿插一些其他视频(原书作者的视频)的知识点。
  • 嗯,生活加油,这段时间好好休养,笔记在更新中..整装待发 ^ _ ^,加油生活…

我年青时以为金钱至上,而今年事已迈,发现果真如此 —王尔德


使用XML配置搭建SSM项目

代码详见:https://github.com/LIRUILONGS/SSM-XML.git

  • 新建一个maven工程,构造SSM目录结构
    在这里插入图片描述
  • 添加依赖,构建配置文件
    在这里插入图片描述
    SpringMVC是Spring的子容器,所以SpringMVC子容器可以访问Spring父容器,反之则不行。所以Spring的配置文件扫描除了Controller的bean,SpringMVC扫描controller的东西。
    在这里插入图片描述

使用 Java配置类搭建SSM项目

代码详见:https://github.com/LIRUILONGS/SSM-java.git

  • @Configuration 注解表示这是一个配置类,在我们这里,这个配置的作用类似于 applicationContext.xml
  • @ComponentScan 注解表示配置包扫描,里边的属性和 xml 配置中的属性都是一一对应的,useDefaultFilters 表示使用默认的过滤器,然后又除去 Controller 注解,即在 Spring 容器中扫描除了 Controller 之外的其他所有 Bean 。
  • 使用 Java 代码去代替 web.xml 文件,这里会用到 WebApplicationInitializer ,WebInit 的作用类似于 web.xml,这个类需要实现 WebApplicationInitializer 接口,并实现接口中的方法,当项目启动时,onStartup 方法会被自动执行,我们可以在这个方法中做一些项目初始化操作,例如加载 SpringMVC 容器,添加过滤器,添加 Listener、添加 Servlet 等。具体定义如下:
    在这里插入图片描述

注意
由于我们在 WebInit 中只是添加了 SpringMVC 的配置,这样项目在启动时只会去加载 SpringMVC 容器,而不会去加载 Spring 容器,如果一定要加载 Spring 容器,需要我们修改 SpringMVC 的配置,在 SpringMVC 配置的包扫描中也去扫描 @Configuration 注解,进而加载 Spring 容器,还有一种方案可以解决这个问题,就是直接在项目中舍弃 Spring 配置,直接将所有配置放到 SpringMVC 的配置中来完成,这个在 SSM 整合时是没有问题的,在实际开发中,较多采用第二种方案,第二种方案,SpringMVC 的配置如下:

  • 静态资源过滤:重写 addResourceHandlers 方法,在这个方法中配置静态资源过滤,这里我将静态资源放在 resources 目录下,所以资源位置是 classpath:/ ,当然,资源也可以放在 webapp 目录下,此时只需要修改配置中的资源位置即可。如果采用 Java 来配置 SSM 环境,一般来说,可以不必使用 webapp 目录,除非要使用 JSP 做页面模板,否则可以忽略 webapp 目录。

  • 视图解析器

    1
    2
    3
    4
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    </bean>
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @ComponentScan(basePackages = "org.javaboy")
    public class SpringMVCConfig extends WebMvcConfigurationSupport {
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
    registry.jsp("/jsp/", ".jsp");
    }
    }
  • 路径映射:控制器的作用仅仅只是一个跳转,就像上面小节中的控制器,里边没有任何业务逻辑,像这种情况,可以不用定义方法,可以直接通过路径映射来实现页面访问。如果在 XML 中配置路径映射

    1
    <mvc:view-controller path="/hello" view-name="hello" status-code="200"/>

    这行配置,表示如果用户访问 /hello 这个路径,则直接将名为 hello 的视图返回给用户,并且响应码为 200,这个配置就可以替代 Controller 中的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @ComponentScan(basePackages = "org.javaboy")
    public class SpringMVCConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/hello3").setViewName("hello");
    }
    }
  • JSON 配置SpringMVC 可以接收JSON 参数,也可以返回 JSON 参数,这一切依赖于 HttpMessageConverter。

    HttpMessageConverter 可以将一个 JSON 字符串转为 对象,也可以将一个对象转为 JSON 字符串,实际上它的底层还是依赖于具体的 JSON 库。
    所有的 JSON 库要在 SpringMVC 中自动返回或者接收 JSON,都必须提供和自己相关的 HttpMessageConverter 。
    SpringMVC 中,默认提供了 Jackson 和 gson 的 HttpMessageConverter ,分别是:MappingJackson2HttpMessageConverter 和 GsonHttpMessageConverter 。
    正因为如此,我们在 SpringMVC 中,如果要使用 JSON ,对于 jackson 和 gson 我们只需要添加依赖,加完依赖就可以直接使用了。具体的配置是在 AllEncompassingFormHttpMessageConverter 类中完成的。
    如果开发者使用了 fastjson,那么默认情况下,SpringMVC 并没有提供 fastjson 的 HttpMessageConverter ,这个需要我们自己提供,如果是在 XML 配置中,fastjson 除了加依赖,还要显式配置 HttpMessageConverter,如下:

1
2
3
4
5
6
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

第1章Spring Boot入门

  • 提供一个快速的Spring项目搭建渠道
  • 开箱即用,很少的Spring 配置就能运行一个Java EE项目。
  • 提供了生产级的服务监控方案。
  • 内嵌服务器,可以快速部署。
  • 提供了一系列非功能性的通用配置。
  • 纯Java配置,没有代码生成,也不需要XML配置。

第2章 Spring Boot基础配置

工程创建的三种方式:

  1. 在线创建
  2. 通过 IDE 来创建(IntelliJ IDEA、STS)
  3. 通过改造一个普通的 Maven 工程来实现

2.1不使用spring-boot-starter-parent

spring-boot-starter-parent主要提供了如下默认配置:

  • Java版本默认使用1.8.编码格式
  • 默认使用UTF-8.
  • 提供Dependency Management进行项目依赖的版本管理。
  • 默认的资源过滤与插件配置

2.2 @Spring BootApplication.

1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...

@Spring BootApplication 是一个组合注解:

  • @SpringBootConfiguration原来就是一个@Configuration,所以@Spring BootConfiguration的功能就是表明这是一个配置类。开发者可以在这个类中配置Bean。从这个角度来讲,这个类所扮演的角色有点类似于Spring中applicationContext.xml文件的角色。
  • 第二个注解@EnableAutoConfiguration表示开启自动化配置。 Spring Boot中的自动化配置是非侵入式的,在任意时刻,开发者都可以使用自定义配置代替自动化配置中的某一个配置。
  • 第三个注解@ComponentScan完成包扫描,也是Spring中的功能。由于@ComponentScan注解默认扫描的类都位于当前类所在包的下面,因此建议在实际项目开发中把项目启动类放在根包。

2.3定制banner

  • Spring Boot项目在启动时会打印一个banne
  • 定制网站:http://patorjk.com/software/taag
    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    public class SpringBootDemoApplication {

    public static void main(String[] args) {
    SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringBootDemoApplication.class);
    builder.bannerMode(Banner.Mode.OFF).run(args);
    }
    }

2.4 Web容器配置

2.4.1 Tomcat配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
##配置了Web容器的端口号。
server.port=8081
##配置了当项目出错时跳转去的页面。
server.error.path=/error
##配置了session失效时间, 30m表示30分钟,如果不写单位,默认单位是秒。由于Tomcat中配置session过期时间以分钟为单位,因此这里单位如果是秒的话,该时间会被转换为一个不超过所配置秒数的最大分钟数,例如这里配置了119,默认单位为秒,则实际session过期时间为1分钟。
server.servlet.session.timeout=30m
##表示项目名称,不配置时默认为/,如果配置了,就要在访问路径中加上配置的路径。
server.servlet.context-path=/
##表示配置Tomcat请求编码。
server.tomcat.uri-encoding=utf-8
##表示Tomcat最大线程数。
server.tomcat.threads.max=500
##是一个存放Tomcat运行日志和临时文件的目录,若不配置,则默认使用系统的临时目录。
server.tomcat.basedir=/home/sang/tmp
##
HTTPS的配置:

在这里插入图片描述

1
2
3
4
5
6
## 密匙文件
server.ssl.key-store=sang.p12
## 密匙别名
server.ssl.key-alias=tomcathttps
## 就是在cmd命令执行过程中输入的密码
server.ssl.key-store-password=123456

Spring Boot不支持同时在配置中启动HTTPHTTPS,这个时候可以配置请求重定向,将HTTP请求重定向为HTTPS请求。配置方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Configuration
public class TomcatConfig {
/*
* @return
* @Description : TODO 配置一个 TomcatServletWebServerFactory 的Bean,
* @author Liruilong
* @date 2021/6/3 11:47
**/
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(){
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
factory.addAdditionalTomcatConnectors(createTomcatConnector());
return factory;
}
/*
* @return
* @Description
* @author Liruilong
* @date 2021/6/3 11:45
**/
private Connector createTomcatConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8081);
return connector;
}
}

这里首先配置一个TomcatServletWebServerFactory,然后添加一个Tomcat中的Connector (监听8080端口) ,并将请求转发到8081上去。

2.4.2 Jetty配置

除了Tomcat外,也可以在Spring Boot中嵌入Jetty,从spring-boot-starter-web中除去默认的Tomcat,然后加入Jetty的依赖即可配置方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.4.3 Undertow配置

Undertow是一个红帽公司开源的Java服务器,具有非常好的性能,在Spring Boot中也得到了很好的支持,配置方式与Jetty类似。

2.5 Properties配置

Spring Boot项目中的·application.properties配置文件一共可以出现在如下4个位置:加载的优先级从1到4依次降低

在这里插入图片描述

  1. 项目根目录下的config文件夹中。
  2. 项目根目录下。
  3. classpath 下的config文件夹中。
  4. classpath 下

application.yml配置文件的优先级与上面一致默认情况下, 如果开发者不想使用application.properties作为配置文件名,也可以自己定义。例如,在resources目录下创建一个配置文件app.properties,然后将项目打成jar包,打包成功后,使用如下命令运行:
在这里插入图片描述

2.6类型安全配置属性.

Spring提供了@Value注解以及EnvironmentAware接口来将Spring Environment中的数据注入到属性上, Spring Boot对此进一步提出了类型安全配置属性(Type-safe ConfigurationProperties) ,这样即使在数据量非常庞大的情况下,也可以更加方便地将配置文件中的数据注入Bean中.
在这里插入图片描述
yml类型配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
my:
users:
- name: 江南一点雨
address: China
favorites:
- 足球
- 徒步
- Coding
- name: sang
address: GZ
favorites:
- 阅读
- 吉他
1
2
3
4
5
6
7
8
9
/**
* Created by sang on 2018/7/5.
*/
@Component
@ConfigurationProperties(prefix = "my")
public class Users {
private List<User> users;
}

2.7 YAML配置

YAMLJSON的超集,简洁而强大,是一种专门用来书写配置文件的语言,可以替代application.properties。在创建一个Spring Boot项目时,引入的spring-boot-starter-web依赖间接地引入了snakeyaml依赖, snakeyaml会实现对YAML配置的解析。YAML的使用非常简单,利用缩进来表示层级关系,并且大小写敏感。在Spring Boot项目中使用YAML只需要在resources目录下创建一个application.yml文件即可,然后向application.yml中添加配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 80
servlet:
context-path: /chapter02
tomcat:
uri-encoding: utf-8

my:
users:
- name: 江南一点雨
address: China
favorites:
- 足球
- 徒步
- Coding
- name: sang
address: GZ
favorites:
- 阅读
- 吉他

2.8 Profile

开发者在项目发布之前,配置需要频繁更改,例如数据库配置、redis配置、mongodb配置、jms配置等。频·繁修改带来了巨大的工作量, Spring对此提供了解决方案(@Profile注解) , Spring Boot则更进一步提供了更加简洁的解决方案, Spring Boot中约定的不同环境下配置文件名称规则为application-{profile}.properties, profile占位符表示当前环境的名称,具体配置步骤如下:

不同的环境指定不同的配置文件
1
spring.profiles.active=dev

在这里插入图片描述


第3章Spring Boot整合视图层技术

Spring Boot官方推荐使用的模板引擎是Thymeleaf,不过像FreeMarker也支持, JSP技术在这里并不推荐使用。下面分别向读者介绍Spring Boot整合Thymeleaf和FreeMarker两种视图层技术。

3.1整合Thymeleaf

Thymeleaf是新一代Java模板引擎,类似于Velocity, FreeMarker等传统Java模板引擎。与传统Java模板引擎不同的是, Thymeleaf支持HTML原型,既可以让前端工程师在浏览器中直接打开查看样式,也可以让后端工程师结合真实数据查看显示效果。同时, Spring Boot提供了Thymeleaf自动化配置解决方案,因此在Spring Boot中使用Thymeleaf非常方便。Spring Boot整合Thymeleaf主要可通过如下步骤:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.配置Thymeleaf

Spring Boot为Thymeleaf提供了自动化配置类ThymeleafAutoConfiguration,相关的配置属性在ThymeleatProperties类中, ThymeleafProperties部分源码如下:
在这里插入图片描述
如果开发者想对默认的Thymeleaf配置参数进行自定义配置,那么可以直接在application.properties中进行配置,部分常见配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#是否开启缓存,开发时可设置为false,默认为true
spring.thymeleaf.cache=true
#是否检查模板是否存在,默认为true
spring.thymeleaf.check-template=true
#是否检查模板位置是否存在,默认为true
spring.thymeleaf.check-template-location=true
#模板文件编码
spring.thymeleaf.encoding=UTF-8
#模板文件位置
spring.thymeleaf.prefix=classpath:/templates/
#Content-Type配置
spring.thymeleaf.servlet.content-type=text/html
#模板文件后缀
spring.thymeleaf.suffix=.html

在这里插入图片描述
在这里插入图片描述
官网: https://www.thymeleaf.org

3.2整合FreeMarke

FreeMarker是一个非常古老的模板引擎,可以用在Web环境或者非Web环境中。与Thymeleaf不同, FreeMarker需要经过解析才能够在浏览器中展示出来。FreeMarker不仅可以用来配置HTML页面模板,也可以作为电子邮件模板配置文件模板以及源码模板等。Spring Boot中对FreeMarker整合也提供了很好的支持.

配置FreeMarker

Spring Boot对FreeMarker也提供了自动化配置类FreeMarkerAutoConfiguration,相关的配置属性在FreeMarkerProperties 中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#HttpServletRequest的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-request-override=false
#HttpSession的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-session-override=true
#是否开启缓存
spring.freemarker.cache=fal se
#模板文件编码
spring.freemarker.charset=UTF-8
#是否检查模板位置
spring.freemarker.check-template-location=true
#Content-Type的值
spring.freemarker.content-type=text/html
#是否将HttpServletRequest中的属性添加到Model中
spring.freemarker.expose-request-attributes=false
#是否将HttpSession中的属性添加到Model中
spring.freemarker.expose-session-attributes=true
#模板文件后缀
spring.freemarker.suffix=.ftl
#模板文件位置
spring.freemarker.template-loader-path=classpath:/templates/

在这里插入图片描述
在这里插入图片描述
官网:https://freemarker.apache.org/

3.3 整合 JSP

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.liruilong.spring_boot_demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Classname WebMvcConfig
* @Description TODO
* @Date 2021/6/4 10:02
* @Created Li Ruilong
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
}

第4章 Spring Boot整合Web开发.

4.1返回JSON数据

4.1.1 默认实现

JSON是目前主流的前后端数据传输方式, Spring MVC中使用消息转换器HttpMessageConverter对JSON的转换提供了很好的支持,在Spring Boot中更进一步,对相关配置做了更进一步的简化。默认情况下,当开发者新创建一个Spring Boot项目后,添加Web依赖,
在这里插入图片描述
这个依赖中默认加入了jackson-databind作为JSON处理器,此时不需要添加额外的JSON处理器就能返回一段JSON了.

如果需要频繁地用到@ResponseBody注解,那么可以采用@RestController 组合注解代替@Controller@ResponseBody

这是Spring Boot自带的处理方式。如果采用这种方式,那么对于字段忽略、日期格式化等常见需求都可以通过注解来解决。这是通过Spring中默认提供的MappingJackson2HttpMessageConverter来实现的.
HttpMessageConverter ,看名字就知道,这是一个消息转换工具,有两方面的功能:

  1. 将服务端返回的对象序列化成 JSON 字符串
  2. 将前端传来的 JSON 字符串反序列化成 Java 对象
    所有的 JSON 生成都离不开相关的 HttpMessageConverter,SpringMVC 自动配置了 JacksonGson 的 HttpMessageConverter,Spring Boot 中又对此做了自动化配置:
  3. org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration
  4. org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration

所以,如果用户使用 jacksongson 的话,没有其他额外配置,则只需要添加依赖即可。

修改转化器

添加一个MappingJackson2HttpMessageConverter,由@ConditionalOnMissingBean确定。
嗯,我们温习一下条件化注解吧

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然这里我们也可以只定义一个 ObjectMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.liruilong.spring_boot_demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;


import java.text.SimpleDateFormat;

/**
* @Classname WebMvcConfig
* @Description TODO
* @Date 2021/6/4 10:02
* @Created Li Ruilong
*/
@Configuration
public class WebMvcConfig {

@Bean
MappingJackson2HttpMessageConverter mappingJackson2CborHttpMessageConverter(){
return new MappingJackson2HttpMessageConverter(objectMapper());
}

@Bean
ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return om;
}

}
4.1.2 自定义转换器

当然开发者在这里也可以根据实际需求自定义JSON转换器。常见的JSON处理器除了jackson-databind之外,还有Gsonfastison,这里针对常见用法分别举例.

Gson

Gson是Google的一个开源JSON解析框架。使用Gson,需要先除去默认的jackson-databind,然后加入Gson依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>

由于Spring Boot中默认提供了Gson的自动转换类GsonHttpMessageConvertersConfiguration,因此Gson的依赖添加成功后,可以像使用jackson-databind那样直接使用Gson。但是在Gson进行·转换时,如果想对日期数据进行格式化,那么还需要开发者自定义HttpMessageConverter.自定义HttpMessageConverter可以通过如下方式。也可以直接使用Gson对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class WebMvcConfig {
// @Bean
// GsonBuilder gsonBuilder() {
// GsonBuilder gsonBuilder = new GsonBuilder();
// gsonBuilder.setDateFormat("yyyy-MM-dd");
// return gsonBuilder;
// }
@Bean
GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat("yyyy-MM-dd");
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gsonBuilder.create());
return converter;
}
}
fastison

fastjson是阿里巴巴的一个开源JSON解析框架,是目前JSON解析速度最快的开源框架,该框架也可以集成到Spring Boot中。不同于Gson, fastjson继承完成之后并不能立马使用,需要开发者提供相应的HttpMessageConverter后才能使用,集成fastison的步骤如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
1
spring.http.encoding.force-response=true

对于FastlsonHttpMessageConverter的配置,除了FastJsonHttpMessageConverter 这种方式之外,还有另一种方式。在Spring Boot项目中,当开发者引入spring-boo-starter-web依赖之后,该依赖又依赖了spring-boot-autoconfigure,在这个自动化配置中,有一个webMvcAutoConfiguration类提供了对Spring MVC最基本的配置,如果某一项自动化配置不满足开发需求,开发者可以针对该项自定义配置,只需要实现WebMveConfigurer接口即可(在Spring 5.0之前是通过继承WebMvcConfigurerAdapter类来实现的) ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
converter.setFastJsonConfig(fastJsonConfig);
converter.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(converter);
}

// @Bean
// FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
// FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
// FastJsonConfig fastJsonConfig = new FastJsonConfig();
// fastJsonConfig.setCharset(Charset.forName("UTF-8"));
// fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// converter.setFastJsonConfig(fastJsonConfig);
// converter.setDefaultCharset(Charset.forName("UTF-8"));
// return converter;
// }
}

4.2静态资源访问

Spring MVC中,对于静态资源都需要开发者手动配置静态资源过滤。Spring Boot中对此也提供了自动化配置,可以简化静态资源过滤配置。

Spring MVC中的配置:

xml

1
2
3
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/html/**" location="/html/"/>

由于这是一种Ant风格的路径匹配符,/** 表示可以匹配任意层级的路径,因此上面的代码也可以像下面这样简写:

1
<mvc:resources mapping="/**" location="/"/>

java:重写 WebMvcConfigurationSupport 类中的addResourceHandlers方法,在该方法中配置静态资源位置即可

4.2.1默认策略

Spring Boot中对于Spring MVC的自动化配置都在webMvcAutoConfiguration类中,因此对于默认的静态资源过滤策略可以从这个类中一窥究竟。在WebMvcAutoConfiguration类中有一个静态内部类webMvcAutoConfigurationAdapter,实现了4.1节提到的WebMvcConfigurer接口。webMvcConfigurer接口中有一个方法addResourceHandlers是用来配置静态资源过滤的。方法在WebMvcAutoConfigurationAdapter类中得到了实现,部分核心代码如下
在这里插入图片描述
Spring Boot在这里进行了默认的静态资源过滤配置,其中staticPathPattern默认定义在WebMvcProperties
在这里插入图片描述
registration.addResourceLocations(this.resourceProperties.getStaticLocations());获取到的默认静态资源位置定义在ResourceProperties
在这里插入图片描述

在一个新创建的Spring Boot项目中,添加了spring-boot-starter-web依赖之后,在resources目录下分别创建4个目录, 4个目录中放入同名的静态资源(如图4-4所示,数字表示不同位置资源的优先级)
在这里插入图片描述

4.2.2自定义策略

自定义静态资源过滤策略有以下两种方式;

  1. 在配置文件中定义可以在application.properties中直接定义过滤规则和静态资源位置,

    1
    2
    3
    4
    # 静态资源位置
    spring.web.resources.static-locations=classpath:/static/
    # 过滤规则
    spring.mvc.static-path-pattern=/static/**

    在这里插入图片描述

  2. Java编码定义也可以通过Java编码方式来定义,此时只需要实现WebMveConfigurer接口即可,然后实现该接口的addResourceHandlers方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    }

4.3文件上传.

Spring MVC对文件上传做了简化,在Spring Boot中对此做了更进一步的简化,文件上传更为方便。Java中的文件上传一共涉及两个组件,一个是CommonsMultipartResolver,另一个是StandardServletMultipartResolver.
其中CommonsMultipartResolver使用commons-fileupload来处理multipart请求,而StandardServletMultipartResolver则是基于Servlet 3.0来处理multipart请求的,因此若使用StandardServletMultipartResolver,则不需要添加额外的jar包。Tomcat 7.0开始就支持Servlet3.0.

Spring Boot提供的文件上传自动化配置类MultiparAutoConfiguraton中,默认也是采用StandardServletMultipartResolver
在这里插入图片描述

1
2
spring.servlet.multipart.max-file-size=1KB
.....
4.3.1单文件上传
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("/yyyy/MM/dd/");
@PostMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest req) {
String realPath = req.getServletContext().getRealPath("/");
String format = LocalDate.now().format(dateTimeFormatter);
String path = realPath + format;
File folder = new File(path);
if (!folder.exists()) {
folder.mkdirs();
}
String oldName = file.getOriginalFilename();
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
try {
file.transferTo(new File(folder, newName));
String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
return s;
} catch (IOException e) {
e.printStackTrace();
}
return "error";
}
4.3.2多文件上传
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload2" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<input type="submit" value="上传">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@PostMapping("/upload2")
public void upload(MultipartFile[] files, HttpServletRequest req) {
String realPath = req.getServletContext().getRealPath("/");
String format = LocalDate.now().format(dateTimeFormatter);
String path = realPath + format;
File folder = new File(path);
if (!folder.exists()) {
folder.mkdirs();
}
try {
for (MultipartFile file : files) {
String oldName = file.getOriginalFilename();
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
file.transferTo(new File(folder, newName));
String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
System.out.println("s = " + s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload3" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="file" name="file2">
<input type="submit" value="上传">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/upload3")
public void upload(MultipartFile file1, MultipartFile file2, HttpServletRequest req) {
String realPath = req.getServletContext().getRealPath("/");
String format = LocalDate.now().format(dateTimeFormatter);
String path = realPath + format;
File folder = new File(path);
if (!folder.exists()) {
folder.mkdirs();
}
try {
String oldName1 = file1.getOriginalFilename();
String newName1 = UUID.randomUUID().toString() + oldName1.substring(oldName1.lastIndexOf("."));
file1.transferTo(new File(folder, newName1));
String s1 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName1;
System.out.println("s1 = " + s1);

String oldName2 = file2.getOriginalFilename();
String newName2 = UUID.randomUUID().toString() + oldName2.substring(oldName2.lastIndexOf("."));
file2.transferTo(new File(folder, newName2));
String s2 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName2;
System.out.println("s2 = " + s2);
} catch (IOException e) {
e.printStackTrace();
}
}
4.3.3AJAX文件上传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
</head>
<body>
<div id="result"></div>
<input type="file" id="file">
<input type="button" value="上传" onclick="uploadFile()">
<script>
function uploadFile() {
var file = $("#file")[0].files[0];
var formData = new FormData();
formData.append("file", file);
formData.append("username", "javaboy");
$.ajax({
type:'post',
url:'/upload',
processData:false,
contentType:false,
data:formData,
success:function (msg) {
$("#result").html(msg);
}
})
}
</script>
</body>
</html>

4.4 @ControllerAdvice

顾名思义, @ControllerAdvice就是@Controller的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandier. @ModelAttribute以及@InitBinder使用。

4.4.1 全局异常处理

上传文件大小超出限制。

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice
//@Controller
//@RestControllerAdvice
//@RestController
public class MyGlobalException {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ModelAndView customException(MaxUploadSizeExceededException e) {
ModelAndView mv = new ModelAndView("javaboy");
mv.addObject("error", e.getMessage());
return mv;
}
}
4.4.2 添加全局数据

@ControllerAdvice是一个全局数据处理组件,因此也可以在@ControllerAdvice中配置全局数据,使用@ModelAtribute注解进行配置,代码如下:

1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
public class MyGlobalData {
@ModelAttribute("info")
public Map<String,String> mydata() {
Map<String, String> info = new HashMap<>();
info.put("username", "javaboy");
info.put("address", "www.javaboy.org");
return info;
}
}

在全局配置中添加mydata方法,返回一个map.该方法有一个注解@ModelAttribute,其中的value属性表示这条返回数据的key,而方法的返回值是返回数据的value,此时在任意请求的Controller中,通过方法参数中的Model都可以获取info的数据。

4.4.3 请求参数预处理

@ControllerAdvice结合@InitBinder还能实现请求参数预处理,即将表单中的数据绑定到实体类上时进行一些额外处理。
多个实体类存在相同的字段时,会合并字段值,使用ControllerAdvice来做预处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
@ControllerAdvice
public class MyGlobalData {

@InitBinder("b")
public void b(WebDataBinder binder) {
binder.setFieldDefaultPrefix("b.");
}
@InitBinder("a")
public void a(WebDataBinder binder) {
binder.setFieldDefaultPrefix("a.");
}

}
1
2
3
4
5
6
7
8
9
@RestController
public class BookController {

@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
System.out.println("book = " + book);
System.out.println("author = " + author);
}
}

在GlobalConfig类中创建两个方法,

  • 第一个@InitBinder(“b”)表示该方法是处理@ModelAttribute(“)对应的参数的,
  • 第二个@nitBinder(“a”)表示该方法是处理@ModelAttribute(“a”)对应的参数的。

在WebDataBinder对象中,还可以设置允许的字段、禁止的字段、必填字段以及验证器等。

4.5 自定义错误页

Spring Boot中的全局异常处理。在处理异常时,开发者可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如Filter 中抛出异常,使用@ControllerAdvice定义的全局异常处理机制就无法处理

因此, Spring Boot中对于异常的处理还有另外的方式,这就是本节要介绍的内容。在Spring Boot 中,默认情况下,如果用户在发起请求时发生了404错误, Spring Boot会有一个默认的页面展示给用户.
在这里插入图片描述
Spring Boot中的错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:
在这里插入图片描述
·errorHtml方法用来返回错误HTML页面, error用来返回错误JSON,具体返回的是HTML还是JSON,则要看请求头的Accept参数。返回JSON的逻辑很简单,不必过多介绍,返回HTML的逻辑稍微有些复杂,在errorHtml方法中,通过调用resolveErrorView方法来获取一个错误视图的ModelAndView,而resolveErrorView方法的调用最终会来到DefaultErrorViewResolver类中。DefaultErrorViewResolver类是Spring Boot中默认的错误信息视图解析器,部分源码如下:
在这里插入图片描述

4.5.1 简单配置.静态页面

自定义错误页面其实很简单,提供4xx和Sxx页面即可。如果开发者不需要向用户展示详细的错误信息,那么可以把错误信息定义成静态页面,直接,在resources/static 录下创建error目录,然后在error目录中创建错误展示页面。错误展示页面的命名规则有两种:
在这里插入图片描述

  • 一种是4xx.html5xx.html;
  • 另一种是直接使用响应码命名文件,例如404.html.405.html, 500.html.第二种命名方式划分得更细,当出错时,不同的错误会展示不同的错误页面
.模板页面

Spring Boot在这里一共返回了5条错误相关的信息,分别是timestamp, status, error, message以及path
在这里插入图片描述
若用户定义了多个错误页面,则响应码html页面的优先级4xx.html. Sxx.tml页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html;动态页面的优级静态页面,即若resources/templatesresource/static 同时定义了4xx.html,则优先展示resources/templates/4xx.html.

4.5.2 复杂配置

上面这种配置还是不够灵活,只能定义HTML页面,无法处理JSON的定制。Spring Boot中支持对Error信息的深度定制,接下来将从三个方面介绍深度定制:自定义Error数据自定义Error视图以及完全自定义

1.自定义Error数据

自定义Error数据就是对返回的数据进行自定义。Spring Boot返回的Error信息一共有5条,分别是timestamp, status, error, message以及path,在BasicErrorController的errorHtml方法和error方法中,都是通过getErrorAttributes方法获取Error信息的。该方法最终会调用到DefaultErrorAttributes类getErrorAttributes方法,而DefaultErrorAttributes类是在ErrorMvcAutoConfiguration中默认提供的.ErrorMvcAutoConfiguration类的errorAttributes方法源码如下:
在这里插入图片描述
源码中可以看出,当系统没有提供ErrorAttributes时才会采用DefaultErrorAttributes.因此自定义错误提示时,只需要自己提供一个ErrorAttributes 可,而DefaultErroAttributes是ErrorAttributes的子类,因此只需要继承DefaultErrorAttributes即可

1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyErrorAtributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(webRequest, options);
if ((Integer) map.get("status") == 404) {
map.put("message", "页面不存在");
}
return map;
}
}
2,自定义Error视图

Error视图是展示给用户的页面,在BasicErrorController errorHtml方法中调用resolveErrorView方法获取一个ModelAndView实例。 resolveErrorView方法是由ErrorViewResolver提供的,通过ErrorMvcAutoConfiguration类的源码可以看到Spring Boot默认采用的ErrorViewResolver是DefaultErrorViewResolver. ErrorMvcAutoConfiguration部分源码如下:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
public MyErrorViewResolver(ApplicationContext applicationContext, WebProperties.Resources resources) {
super(applicationContext, resources);
}

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
Map<String, Object> map = new HashMap<>();
map.putAll(model);
if ((Integer) model.get("status") == 500) {
map.put("message", "服务器内部错误");
}
ModelAndView view = new ModelAndView("javaboy/999",map);
return view;
}
}
3,完全自定义

前面提到的两种自定义方式都是对BasicErrorController类中的某个环节进行修补。查看Error自动化配置类ErrorMvcAutoConfiguration,读者可以发现BasicErrorController 身只是一个默认的配置,相关源码如下:
在这里插入图片描述
从这段源码中可以看到,若开发者没有提供自己的ErrorController,则Spring Boot提供BasicErrorController作为默认的ErrorController,因此,如果开发者需要更加灵活地对Error视图数据进行处理,那么只需要提供自己的ErrorController即可。提供自己的ErrorController有两种方式:一种是实现ErrorController接口,另一种是直接继承BasicErrorController,由于ErorController接口只提供一个待实现的方法,而BasicErrorController已经实现了很多功能,因此这里选择第二种方式,即通过继承BasicErrorController来实现自己的ErrorController.具体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller
public class MyErrorController extends BasicErrorController {
@Autowired
public MyErrorController(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("custommsg", "出错啦!");
ModelAndView modelAndView = new ModelAndView("myErrorPage", model, status);
return modelAndView;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
body.put("custommsg", "出错啦!");
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
}
  • 自定义MyErrorController继承自BasicErrorController并添加@Controller注解,将MyErrorController 注册Spring MVC容器中·
  • 由于BasicErrorController没有无参构造方法,因此在创建BasicErrorController实例时需要传递参数,在MyErrorController的构造方法上添加@Autowired注解注入所需参数
  • 参考BasicErrorController中的实现,重写errorHtml和error方法,对Error的视图和数据进行充分的自定义。

4.6 CORS支持

CORS (Cross-Origin Resource Sharing)是由w3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。在Java EE开发中,最常见的前端跨域请求解决方案是JSONP,但是JSONP只支持GET请求,这是一个很大的缺陷,而CORS则支持多种HTTP请求方法。以CORS
在这里插入图片描述

响应头中有一个Access-Control-Allow-Origin字段,用来记录可以访问该资源的域。当浏览器收到这样的响应头信息之后,提取出Access-Control-Allow-Origin字段中的值,发现该值包含当前页面所在的,就知道这个跨域是被允许的,因此就不再对前端的跨域请求进行·限制。这就是GET请求的整个跨域流程,在这个过程中,前端请求的代码不需要修改,主.要是后端进行处理。这个流程主要是针对GET, POST以及HEAD请求,并且没有自定义请求头,如果用户发起一个DELETE请求、PUT请求或者自定义了请求头,流程就会稍微复杂一些。

DELETE请求为例,当前端发起一个DELETE请求时,这个请求的处理会经过两个步骤
第一步:发送一个OPTIONS请求。代码如下:
在这里插入图片描述
这个请求将向服务端询问是否具备该资源的DELETE权限,服务端会给浏览器一个响应,代码如下:
在这里插入图片描述
服务端给浏览器的响应, Allow头信息表示服务端支持的请求方法,这个请求相当于一个探测请求,当浏览器分析了请求头字段之后,知道服务端支持本次请求,则进入第二步。
第二步:发送DELETE请求。接下来浏览器就会发送一个跨域的DELETE请求。

在传统的Java EE开发中,可以通过过滤器统一配置,而Spring Boot中对此则提供了更加简洁的解决方案。在Spring Boot中配置CORS的步骤如下:

3.配置跨域

跨域有两个地方可以配置:

  1. 一个是直接在相应的请求方法上加注解: 这种配置方式是一种细粒度的配置.可以控制到每一个方法上。
  • @CrossOrigin中的value表示支持的,这里表示来自http://ocalhost:8081域的请求是支持跨域的.
  • maxAge表示探测请求有效期,在前面的讲解中,读者已经了解到对于DELETE, PUT请求或者有自定义头信息的请求,在执行过程中会先发送探测请求,探测请求不用每次都发送,可以配置一个有效期,有效期过了之后才会发送探测请求。这个属性默认是1800秒,即30分钟。
  • allowedHeaders表示允许的请求头, *表示所有的请求头都被允许
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/book")
public class BookController {
@PostMapping("/")
@CrossOrigin(value = "http://localhost:8081"
,maxAge = 1800,allowedHeaders = "*")
public String addBook(String name) {
return "receive:" + name;
}
@DeleteMapping("/{id}")
@CrossOrigin(value = "http://localhost:8081"
,maxAge = 1800,allowedHeaders = "*")
public String deleteBookById(@PathVariable Long id) {
return String.valueOf(id);
}
}
  1. 另一种全局配置,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
    .allowedHeaders("*")
    .allowedMethods("*")
    .allowedOrigins("http://localhost:8081")
    .maxAge(1800);
    }
    全局配置需要自定义类实现WebMvcConfigurer接口,然后实现接口中的addCorsMappings方法。,在addCorsMappings方法中
  • addMapping表示对哪种格式的请求路径进行跨域处理;
  • allowedHeaders表示允许的请求头,默认允许所有的请求头信息;
  • allowedMethods表示允许的请求方法,默认是GET. POST和HEAD,*表示支持所有的请求方法;
  • maxAge表示探测请求的有效期;
  • allowedOrigins表示支持的域。

4.7配置类与XML配置.

Spring Boot推荐使用Java来完成相关的配置工作。在项目中,不建议将所有的配置放在一个配置类中,可以根据不同的需求提供不同的配置类,例如专门处理Spring Security的配置类提供Bean的配置类Spring MVC相关的配置类。这些配置类上都需要添加@Configuration注解
@ComponentScan注解会扫描所有的Spring组件,也包括@Configuration@ComponentScan注解在项目入口类的@Spring BootApplication注解中已经提供,因此在实际项目中只需要按需提供相关配置类即可。Spring Boot中并不推荐使用XML配置,建议尽量用Java配置代替XML配置,本书中的案例都是以Java配置为主。

如果开发者需要使用XML配置,只需在resources目录下提供配置文件,然后通过@ImportResource加载配置文件即可。例如,有一个Book类如下:
在这里插入图片描述

4.8注册拦截器

Spring MVC中提供了AOP风格的拦截器,拥有更加精细的拦截处理能力。Spring Boot中拦.截器的注册更加方便,步骤如下:

创建拦截器实现Handlerinterceptor接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ublic class MyInterceptor implements HandlerInterceptor {

//该方法返回 false,请求将不再继续往下走
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}

//Controller 执行之后被调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}

//preHandle 方法返回 true,afterCompletion 才会执行。
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}

拦截器中的方法将按preHandle-Controller-postHandle-afterCompletion的顺序执行。注意,只有preHandle方法返回true时后面的方法才会执行。当拦截器链内存在多个拦截器时, postHandler在拦截器链内的所有拦截器返回成功时才会调用,而afterCompletion只有preHandle返回true才调用,但若拦截器链内的第一个拦截器的preHandle方法返回false,则后面的方法都不会执行。

配置拦截器。定义配置类进行拦截器的配置,代码如下:

自定义类实现webMveConfigurer接口,实现接口中的addInterceptors方法。其中,addPathPatterns 表示拦截路径, excludePathPatterns表示排除的路径

1
2
3
4
5
6
7
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/hello");
}
}

4.9启动系统任务

有一些特殊的任务需要在系统启动时执行,例如配置文件加载数据库初始化等操作。如果没有使用Spring Boot,这些问题可以在Listener中解决。Spring Boot对此提供了两种解决方案:CommandLineRunnerApplicationRunner. CommandLineRunnerApplicationRunner基本一致,差别主要体现在参数上。

4.9.1 CommandLineRunner

Spring Boot项目在启动时会遍历所有CommandLineRunner的实现类并调用其中的run方法,如果整个系统中有多个CommandLineRunner的实现类,那么可以使用@Order注解对这些实现类的调用顺序进行排序。
在这里插入图片描述

4.9.2 ApplicationRunner

ApplicationRunner的用法和CommandLineRunner基本一致,区别主要体现在run方法的参数上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Order(98)
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
//获取没有键的参数,获取到的值和 commandlinerunner 一致
List<String> nonOptionArgs = args.getNonOptionArgs();
System.out.println("nonOptionArgs1 = " + nonOptionArgs);
Set<String> optionNames = args.getOptionNames();
for (String optionName : optionNames) {
System.out.println(optionName + "-1->" + args.getOptionValues(optionName));
}
//获取命令行中的所有参数
String[] sourceArgs = args.getSourceArgs();
System.out.println("sourceArgs1 = " + Arrays.toString(sourceArgs));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Order(97)
public class MyApplicationRunner2 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
//获取没有键的参数,获取到的值和 commandlinerunner 一致
List<String> nonOptionArgs = args.getNonOptionArgs();
System.out.println("nonOptionArgs2 = " + nonOptionArgs);
Set<String> optionNames = args.getOptionNames();
for (String optionName : optionNames) {
System.out.println(optionName + "-2->" + args.getOptionValues(optionName));
}
//获取命令行中的所有参数
String[] sourceArgs = args.getSourceArgs();
System.out.println("sourceArgs2 = " + Arrays.toString(sourceArgs));
}
}

@Order注解依然是用来描述执行顺序的,数字越小越优先执行。不同于CommandLineRunner中run方法的String数组参数,这里run方法的参数是一个ApplicationArguments对象,如果想从ApplicationArguments对象中获取入口类中1main方法1接收的参数,调用ApplicationArguments中的getNonOptionArgs方法即可. ApplicationArguments中的getOptionNames方法用来获取项目启动命令行中参数的key,例如将本项目打成jar包,运行java-jar xxx.jar-name-Michael命令来启动项目,此时getOptionNames方法获取到的就是name,而getOptionValues方法则是获取相应的value.

4.10整合Servlet, Filter和Listener.

Spring Boot中对于整合这些基本的Web组件也提供了很好的支持。在一个Spring Boot Web项目中添加如下三个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig){
System.out.println("MyFilter>>>init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter>>>doFilter");
chain.doFilter(request,response);
}

@Override
public void destroy() {
System.out.println("MyFilter>>>destroy");
}
}
1
2
3
4
5
6
7
8
9
10
11
@WebListener
public class MyListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("MyListener>>>requestDestroyed");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("MyListener>>>requestInitialized");
}
}
1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp){
System.out.println("name>>>"+req.getParameter("name"));
}
}

启动类需要的配置:
在项目入口类上添加@ServletComponentScan注解,实现对ServletFilter以及Listener的扫描,代码如下:

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@ServletComponentScan("org.javaboy.filter")
public class FilterApplication {

public static void main(String[] args) {
SpringApplication.run(FilterApplication.class, args);
}

}

4.11 路径映射.

有一些页面在控制器中不需要加载数据,只是完成·简单的跳转,对于这种页面,可以直接配置路径映射,提高访问速度。例如,有两个Thymeleaf做模板的页面login.htmlindex.tml,直接在MVC配置中重写addViewControllers方法配置映射关系即可:

1
2
3
4
5
6
7
8
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// registry.addViewController("/02").setViewName("02");
registry.addViewController("/02").setViewName("02");
}
}

4.12 配置AOP

4.12.1 AOP简介
AOP中的相关知识
4.12.2 Spring Boot支持

Spring BootSpring的基础上对AOP的配置提供了自动化配置解决方案spring-boot-starter-aop,使开发者能够更加便捷地在Spring Boot项目中使用AOP,配置步骤如下。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* org.javaboy.aop.service.*.*(..))")
public void pc1() {

}

@Before("pc1()")
public void before(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + " 方法开始执行了...");
}

@After("pc1()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + " 方法执行结束了...");
}

@AfterReturning(value = "pc1()", returning = "s")
public void afterReturning(JoinPoint jp, String s) {
String name = jp.getSignature().getName();
System.out.println(name + " 方法的返回值是 " + s);
}

@AfterThrowing(value = "pc1()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
String name = jp.getSignature().getName();
System.out.println(name + " 方法抛出了异常 " + e.getMessage());
}

@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) {
try {

//类似于反射中的 invoke 方法
Object proceed = pjp.proceed();
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}

4.13 其他

4.13.1 自定义欢迎页

Spring Boot项目在启动后,首先会去静态资源路径下查找index.html作为首页文件,若查找不到,则会去查找动态的index文件作为首页文件.

  • 使用静态的index.html页面作为项目首页,那么只需在resources/static目录下创建index.html文件即可。
  • 若想使用动态页面作为项目首页,则需在resources/templates目录下创建index.html (使用Thymeleaf模板)或者index.fl (使用FreeMarker模板) ,然后在Controller中返回逻辑视图名,代码如下:
1
2
3
4
5
6
7
8
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");

}
}
4.13.2 自定义favicon

favicon.ico是浏览器选项卡左上角的图标,可以放在静态资源路径下或者类路径下,静态资源路径下的favicon.ico优先级高于类路径下的favicon.ico
在线转换网站http:/inaconvert.com/cn/convert-to-ico.php将一张普通图片转为.ico图

4.13.3 除去某个自动配置

Spring Boot中提供了大量的自动化配置类,例如上文提到过的ErrorMvcAutoConfigurationThymeleafAutoConfiguration, FreeMarkerAutoConfiguration, MultipartAutoConfiguration等,这些自动化配置可以减少相应操作的配置,达到开箱即用的效果。在Spring Boot的入口类上有一个@Spring BootApplication注解。该注解是一个组合注解, 由@Spring BootConfiguration@EnableAutoConfiguration以及@ComponentScan组成,其中@EnableAutoConfiguration注解开启自动化配置,相关的自动化配置类就会被使用。如果开发者不想使用某个自动化配置,按如下方式除去相关配置即可.

1
2
3
4
5
6
7
@SpringBootApplication
@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class OtherApplication {
public static void main(String[] args) {
SpringApplication.run(OtherApplication.class, args);
}
}
4.13.4 使用类型转化器
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyDateConverter implements Converter<String, Date> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date convert(String source) {
try {
return sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}

第5章Spring Boot整合持久层技术.

Spring Boot中对常见的持久层框架都提供了自动化配置,例如JabcTemplate, JPA等, MyBatis自动化配置则是MyBatis官方提供的。

5.1 整合JdbcTemplate

JdbcTemplateSpring提供的一套JDBC模板框架,利用AOP技术来解决直接使用JDBC时大量重复代码的问题。JdbcTemplate虽然没有MyBatis那么灵活,但是比直接使用JDBC要方便很多。Spring Boot中对JdbcTemplate的使用提供了自动化配置类JdbcTemplateConfiguration,部分源码如下:

1
2
3
4
5
6
7
8
9
10
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {

@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
JdbcProperties.Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}

需要的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

spring-bool-starter-jdbc 中提供了spring-jdbc,另外还加入了数据库驱动依赖数据库连接池依赖

1
2
3
4
5
# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
  • 创建BookDao,注入JdbcTemplate.由于已经添加了spring-jdbc相关的依赖, JabcTemplate会被自动注册Spring容器中,因此这里可以直接注入JdbcTemplate使用。
  • JdbcTemplate中,增删改三种类型的操作主要使用updatebatchUpdate方法来完成.queryqueryForObject方法主要用来完成查询功能。另外,还有execute方法可以用来执行任意的sQL. call方法用来调用存储过程等。
  • 在执行查询操作时,需要有一个RowMapper将查询出来的列和实体类中的属性-一对应起来。如果列名属性名都是相同的,那么可以直接使用BeanPropertyRowMapper;如果列名和属性名不同,就需要开发者自己实现RowMapper接口,将列和实体类属性-一对应起来。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Repository
    public class BookDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    public int addBook(Book book) {
    return jdbcTemplate.update("INSERT INTO book(name,author) VALUES (?,?)",
    book.getName(), book.getAuthor());
    }
    public int updateBook(Book book) {
    return jdbcTemplate.update("UPDATE book SET name=?,author=? WHERE id=?",
    book.getName(), book.getAuthor(), book.getId());
    }
    public int deleteBookById(Integer id) {
    return jdbcTemplate.update("DELETE FROM book WHERE id=?", id);
    }
    public Book getBookById(Integer id) {
    return jdbcTemplate.queryForObject("select * from book where id=?",
    new BeanPropertyRowMapper<>(Book.class), id);
    }
    public List<Book> getAllBooks() {
    return jdbcTemplate.query("select * from book",
    new BeanPropertyRowMapper<>(Book.class));
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public int addUser2(User user) {
    GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
    int result = jdbcTemplate.update(new PreparedStatementCreator() {
    @Override
    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
    PreparedStatement ps = connection.prepareStatement("insert into user (username,address) values(?,?)", Statement.RETURN_GENERATED_KEYS);
    ps.setString(1, user.getUsername());
    ps.setString(2, user.getAddress());
    return ps;
    }
    }, keyHolder);
    user.setId(keyHolder.getKey().longValue());
    return result;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public List<User> getAllUsers() {
    List<User> list = jdbcTemplate.query("select * from user", new RowMapper<User>() {
    @Override
    public User mapRow(ResultSet resultSet, int i) throws SQLException {
    String username = resultSet.getString("username");
    String address = resultSet.getString("address");
    long id = resultSet.getLong("id");
    User user = new User();
    user.setId(id);
    user.setUsername(username);
    user.setAddress(address);
    return user;
    }
    });
    return list;
    }

5.2整合MyBatis

MyBatis是一款优秀的持久层框架,原名叫作iBaits, 2010年由ApacheSoftwareFoundation迁移到Google Code并改名为MyBatis, 2013年又迁移到GitHub上。MyBatis支持定制化SQL存储过程以及高级映射MyBatis几乎避免了所有的JDBC代码手动设置参数以及获取结果集。在传统的SSM框架整合中,使用MyBatis需要大量的XML配置,而在Spring Boot中, MyBatis官方提供了一套自动化配置方案,可以做到MyBatis开箱即用。具体使用步骤如下。

  • 添加MyBatis依赖、数据库驱动依赖以及数据库连接池
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
         <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
    1
    2
    3
    4
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.url=jdbc:mysql:///chapter05
    spring.datasource.username=root
    spring.datasource.password=123
方法一
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@MapperScan(basePackages = "org.javaboy.mybatis.mapper")
public class MybatisApplication {

public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}

}
  • 一种简单的方式是在配置类上添加@MapperScan("org.sang.mapper")注解,表示扫描org.sang.mapper包下的所有接口作为Mapper,这样就不需要在每个接口上配置@Mapper注解了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public interface UserMapper {
    @Select("select * from user where id=#{id}")
    User getUserById(Long id);

    @Results({
    @Result(property = "address",column = "address1")
    })
    @Select("select * from user")
    List<User> getAllUsers();

    @Insert("insert into user (username,address1) values (#{username},#{address})")
    @SelectKey(statement = "select last_insert_id()",keyProperty = "id",before = false,resultType = Long.class)
    Integer addUser(User user);

    @Delete("delete from user where id=#{id}")
    Integer deleteById(Long id);

    @Update("update user set username=#{username} where id=#{id}")
    Integer updateById(String username, Long id);
    }
方法二

指明该类是一个Mapper:第一种如前面的代码所示,在BookMapper上添加@Mapper注解,表明该接口是一个MyBatis中的Mapper,这种方式需要在每一个Mapper上都添加注解;

1
2
3
4
5
6
7
8
@Mapper
public interface BookMapper {
int addBook(Book book);
int deleteBookById(Integer id);
int updateBookById(Book book);
Book getBookById(Integer id);
List<Book> getAllBooks();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.sang.mapper.BookMapper">
<insert id="addBook" parameterType="org.sang.model.Book">
INSERT INTO book(name,author) VALUES (#{name},#{author})
</insert>
<delete id="deleteBookById" parameterType="int">
DELETE FROM book WHERE id=#{id}
</delete>
<update id="updateBookById" parameterType="org.sang.model.Book">
UPDATE book set name=#{name},author=#{author} WHERE id=#{id}
</update>
<select id="getBookById" parameterType="int" resultType="org.sang.model.Book">
SELECT * FROM book WHERE id=#{id}
</select>
<select id="getAllBooks" resultType="org.sang.model.Book">
SELECT * FROM book
</select>
</mapper>
  • 针对BookMapper接口中的每一个方法都在BookMapper.xml中列出了实现
  • #{}用来代替接口中的参数,实体类中的属性可以直接通过#(实体类属性名}获取。
配置pom.xml文件

Maven工程中, XML配置文件建议写在resources目录下(同包同级目录),当Mapper.xml文件写在包下, ·Maven在运行时会忽略包下的XML文件,因此需要在pom.xml文件中重新指明资源文件位置,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
......
</build>

也可以自定义resourcesmapper位置

1
mybatis.mapper-locations=classpath:mapper/*.xml

5.3整合Spring Data JPA

JPA (Java Persistence API)Spring Data是两个范畴的概念。JPA则是一种ORM规范, JPAHibernate的关系就像JDBCJDBC驱动的关系,即JPA制定了ORM规范,而Hibernate是这些规范的实现(事实上,是先有Hibernate后有JPA, JPA规范的起草者也是Hibernate的作者) ,因此从功能上来说, JPA相当于Hibernate的一个子集。

Spring DataSpring的一个子项目,致力于简化数据库访问,通过规范的方法名称来分析开发者的意图,进而减少数据库访问层的代码量。Spring Data不仅支持关系型数据库,也支持非关系型数据库Spring Data JPA可以有效 简化 关系型数据库访问代码。Spring Boot整合Spring Data JPA的步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.username=root
spring.datasource.password=123
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect
#spring.jpa.properties.database=mysql
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.show-sql= true
  • @Entity注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名。所有的实体类都要有主键,
  • @ld注解表示该属性是一个主键, @GeneratedValue注解表示主键自动生成, strategy则表示主键的生成策略。默认情况下,生成的表中字段的名称就是实体类中属性的名称,通过@Column注解可以定制生成的字段的属性, name表示该属性对应的数据表中字段的名称, nullable表示该字段非空。
  • @Transient注解表示在生成数据库中的表时,该属性被忽略,即不生成对应的字段。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Entity(name = "t_book")
    public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "book_name",nullable = false)
    private String name;
    private String author;
    private Float price;
    @Transient
    private String description;
    //省略getter/setter
    }
  • 自定义BookDao继承自JpaRepository. JpaRepository中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等。
  • 2行定义的方法表示查询以某个字符开始的所有书。·
  • 3行定义的方法表示查询单价大于某个值的所有书。
  • Spring Data JPA中,只要方法的定义符合既定规范, Spring Data就能分析出开发者的意图,从而避免开发者定义SQL所谓的既定规范,就是一定的方法命名规则
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface BookDao extends JpaRepository<Book,Integer>{
    List<Book> getBooksByAuthorStartingWith(String author);
    List<Book> getBooksByPriceGreaterThan(Float price);
    @Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
    Book getMaxIdBook();
    @Query("select b from t_book b where b.id>:id and b.author=:author")
    List<Book> getBookByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
    @Query("select b from t_book b where b.id<?2 and b.name like %?1%")
    List<Book> getBooksByIdAndName(String name, Integer id);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface BookDao extends JpaRepository<Book,Long> {
    List<Book> getBookByAuthorIs(String author);

    @Query(nativeQuery = true,value = "select * from t_book where id=(select max(id) from t_book)")
    Book maxIdBook();

    @Query("update t_book set b_name=:name where id=:id")
    @Modifying
    void updateBookById(String name, Long id);
    }
    支持的命名规则如表所示:
    在这里插入图片描述
    部分方法直接由JpaRepository
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Service
    public class BookService {
    @Autowired
    BookDao bookDao;
    public void addBook(Book book) {
    bookDao.save(book);
    }
    public Page<Book> getBookByPage(Pageable pageable) {
    return bookDao.findAll(pageable);
    }
    public List<Book> getBooksByAuthorStartingWith(String author){
    return bookDao.getBooksByAuthorStartingWith(author);
    }
    public List<Book> getBooksByPriceGreaterThan(Float price){
    return bookDao.getBooksByPriceGreaterThan(price);
    }
    public Book getMaxIdBook(){
    return bookDao.getMaxIdBook();
    }
    public List<Book> getBookByIdAndAuthor(String author, Integer id){
    return bookDao.getBookByIdAndAuthor(author, id);
    }
    public List<Book> getBooksByIdAndName(String name, Integer id){
    return bookDao.getBooksByIdAndName(name, id);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @GetMapping("/findAll")
    public void findAll() {
    PageRequest pageable = PageRequest.of(2, 3);
    Page<Book> page = bookService.getBookByPage(pageable);
    System.out.println("总页数:"+page.getTotalPages());
    System.out.println("总记录数:"+page.getTotalElements());
    System.out.println("查询结果:"+page.getContent());
    System.out.println("当前页数:"+(page.getNumber()+1));
    System.out.println("当前页记录数:"+page.getNumberOfElements());
    System.out.println("每页记录数:"+page.getSize());
    }

5.4多数据源.

所谓多数据源,就是一个Java EE项目中采用了不同数据库实例中的多个库,或者同一个数据库实例中多个不同的库。一般来说,采用MyCat分布式数据库中间件是比较好的解决方案,这样可以把数据库读写分离、分库分表、备份等操作交给中间件去做, Java代码只需要专注于业务即可。不过,这并不意味着无法使用Java代码解决类似的问题,在Spring Framework中就可以配置多数据源, Spring Boot继承其衣钵,只不过配置方式有所变化。

5.4.1 JdbcTemplate多数据源

JdbcTemplate多数据源的配置是比较简单的,因为一个JdbcTemplate对应一个DataSource,开发者只需要手动提供多个DataSource,再手动配置JdbcTemplate即可。具体步骤如下。

1
2
3
4
5
6
7
8
9
10
# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
# 数据源2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=123
spring.datasource.two.url=jdbc:mysql:///chapter05-2
  • DataSourceConfig中提供了两个数据源: dsOnedsTwo,默认方法名即实例名。
  • @ConfigurationProperties注解表示使用不同前缀的配置文件来创建不同的DataSource实例。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.one")
    DataSource dsOne() {
    return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
    return DruidDataSourceBuilder.create().build();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    public class JdbcTemplateConfig {
    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
    }
    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
    }
    }
  • JdbcTemplateConfig中提供两个JdbcTemplate实例。每个JdbcTemplate实例都需要提供-DataSource,由于Spring容器中有两个DataSource实例,因此需要通过方法名查找。@Qualifier注解表示查找不同名称的DataSource实例注入进来
    1
    2
    3
    4
    5
    6
    7
        @Resource(name = "jdbcTemplateOne")
    // @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    @Qualifier("jdbcTemplateTwo")
    JdbcTemplate jdbcTemplateTwo;
    @GetMapping("/test1")
5.4.2 MyBatis多数据源
1
2
3
4
5
6
7
8
9
10
# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
# 数据源2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=123
spring.datasource.two.url=jdbc:mysql:///chapter05-2
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.one")
DataSource dsOne() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.two")
DataSource dsTwo() {
return DruidDataSourceBuilder.create().build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
@MapperScan(basePackages = "org.javaboy.mybatismulti.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
@Autowired
@Qualifier("dsOne")
DataSource ds;

@Bean
SqlSessionFactory sqlSessionFactory1() {
SqlSessionFactory sqlSessionFactory = null;
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
sqlSessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sqlSessionFactory;
}

@Bean
SqlSessionTemplate sqlSessionTemplate1() {
return new SqlSessionTemplate(sqlSessionFactory1());
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@MapperScan(basePackages = "org.javaboy.mybatismulti.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
@Autowired
@Qualifier("dsTwo")
DataSource ds;

@Bean
SqlSessionFactory sqlSessionFactory2() {
SqlSessionFactory sqlSessionFactory = null;
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
sqlSessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sqlSessionFactory;
}

@Bean
SqlSessionTemplate sqlSessionTemplate2() {
return new SqlSessionTemplate(sqlSessionFactory2());
}
}
5.4.3 JPA多数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring.datasource.one.password=123
spring.datasource.one.username=root
spring.datasource.one.url=jdbc:mysql:///chapter05-1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.two.password=123
spring.datasource.two.username=root
spring.datasource.two.url=jdbc:mysql:///chapter05-2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect
spring.jpa.properties.database=mysql
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.show-sql= true

这里的配置与配置单独的JPA有区别,因为在后文的配置中要从JpaProperties中的getProperties方法中获取所有JPA相关的配置, 因此这里的属性前缀都是spring.jpa.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.one")
@Primary
DataSource dsOne() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.two")
DataSource dsTwo() {
return DruidDataSourceBuilder.create().build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "org.sang.dao1",
entityManagerFactoryRef = "entityManagerFactoryBeanOne",
transactionManagerRef = "platformTransactionManagerOne")
public class JpaConfigOne {
@Resource(name = "dsOne")
DataSource dsOne;
@Autowired
JpaProperties jpaProperties;
@Bean
@Primary
LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanOne(
EntityManagerFactoryBuilder builder) {
return builder.dataSource(dsOne)
.properties(jpaProperties.getProperties())
.packages("org.sang.model")
.persistenceUnit("pu1")
.build();
}
@Bean
PlatformTransactionManager platformTransactionManagerOne(
EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean factoryOne = entityManagerFactoryBeanOne(builder);
return new JpaTransactionManager(factoryOne.getObject());
}
}
  • 使用@EnableJpaRepositories注解来进行JPA的配置,该注解中主要配置三个属性:basePackages, entityManagerFactoryRef以及transactionManagerRef.其中, basePackages用来指定Repository所在的位置, entityManagerFactoryRef用来指定实体类管理工厂Bean的名称,transactionManagerRef则用来指定事务管理器的引用名称,这里的引用名称就是JpaConfigOne类中注册的Bean的名称(默认的Bean名称为方法名)
  • 创建LocalContainerEntityManagerFactoryBean,该Bean将用来提供EntityManager实例,在该类的创建过程中,首先配置数据源,然后设置JPA相关配置(JpaProperties由系统自动加载),再设置实体类所在的位置,最后配置持久化单元名,若项目中只有一个EntityManagerFactory, 则persistenceUnit可以省略掉,若有多个,则必须明确指定持久化单元名。
  • 由于项目中会提供两个LocalContainerEntityManagerFactoryBean实例,第12行的注解@Primary表示当存在多个LocalContainerEntityManagerFactoryBean实例时,该实例将被优先使用。
    +PlatformTransactionManager表示创建一个事务管理器。 JpaTransactionManager提供对单个EntityManagerFactory的事务支持,专门用于解决JPA中的事务管理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(basePackages = "org.sang.dao2",
    entityManagerFactoryRef = "entityManagerFactoryBeanTwo",
    transactionManagerRef = "platformTransactionManagerTwo")
    public class JpaConfigTwo {
    @Resource(name = "dsTwo")
    DataSource dsTwo;
    @Autowired
    JpaProperties jpaProperties;
    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanTwo(
    EntityManagerFactoryBuilder builder) {
    return builder.dataSource(dsTwo)
    .properties(jpaProperties.getProperties())
    .packages("org.sang.model")
    .persistenceUnit("pu2")
    .build();
    }
    @Bean
    PlatformTransactionManager platformTransactionManagerTwo(
    EntityManagerFactoryBuilder builder) {
    LocalContainerEntityManagerFactoryBean factoryTwo = entityManagerFactoryBeanTwo(builder);
    return new JpaTransactionManager(factoryTwo.getObject());
    }
    }

第6章Spring Boot整合NosQL

NoSQL是指非关系型数据库,非关系型数据库和关系型数据库两者存在许多显著的不同点,,其中最重要的是NoSQL不使用SQL作为查询语言。其数据存储可以不需要固定的表格模式,一般.都有水平可扩展性的特征。NoSQL主要有如下几种不同的分类:

  • Key/Value键值存储。这种数据存储通常都是无数据结构的,一般被当作字符串或者二进制数据,但是数据加载速度快,典型的使用场景是处理高并发或者用于日志系统等,这一类的数据库有Redis. Tokyo Cabinet等.
  • 列存储数据库。列存储数据库功能相对局限,但是查找速度快,容易进行分布式扩展,一般用于分布式文件系统中,这一类的数据库有HBase, Cassandra等。
  • 文档型数据库	。和`Key/Value`键值存储类似,文档型数据库也没有严格的`数据格式`,这既是缺点也是优势,因为不需要预先创建表结构,数据格式更加灵活,一般可用在`Web`应用中,这一类数据库有`MongoDB`, `CouchDB`等。
    
  • 图形数据库	。图形数据库专注于`构建关系图谱`,例如`社交网络`,`推荐系统`等,这一类的数据库有`Neo4J`、`DEX`等。
    

6.1整合Redis

Redis是一个使用C编写的基于内存NoSQL数据库,它是目前最流行的键值对存储数据库。Redis由一个Key, Value映射的字典构成,与其他NoSQL不同, Redis中Value的类型不局限于字符串,还支持列表集合有序集合散列等。

6.1.1 Redis简介

Redis不仅可以当作缓存使用,也可以配置数据持久化后当作NoSQL数据库使用, 目前支持两种持久化方式:快照持久化AOF持久化。另一方面, Redis也可以搭建集群或者主从复制结构,在高并发环境下具有高可用性

6.1.2 Redis安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Loaded plugins: fastestmirror, product-id, search-disabled-repos, subscription-manager

This system is not registered with an entitlement server. You can use subscription-manager to register.

Repository epel is listed more than once in the configuration
Repository epel-debuginfo is listed more than once in the configuration
Repository epel-source is listed more than once in the configuration
Loading mirror speeds from cached hostfile
* base: mirrors.cloud.aliyuncs.com
* extras: mirrors.cloud.aliyuncs.com
* updates: mirrors.cloud.aliyuncs.com
Package redis-3.2.12-2.el7.x86_64 already installed and latest version
Nothing to do
[root@liruilong ~]#
[root@liruilong ~]# redis-server -v
Redis server v=3.2.12 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=7897e7d0e13773f
[root@liruilong ~]# redis-cli -v
redis-cli 3.2.12
6.1.3 Redis整合Spring Boot

RedisJava客户端有很多,例如JedisJRedisSpring Data Redis等, Spring Boot借助于Spring. Data Redis为Redis提供了开箱即用自动化配置,开发者只需要添加相关依赖并配置Redis连接信息即可,具体整合步骤如下。

添加如下依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

默认情况下, spring-boot-starter-data-redis使用的Redis工具是Lettuce,考虑到有的开发者习惯使用Jedis,因此可以从spring-boot-starter-data-redis中排除Lettuce并引入Jedis,修改为如下依赖:

配置Redis接下来在application.properties 中配置Redis连接信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#基本连接信息配置

#表示使用的Redis库的编号, Redis中提供了16个database,编号为0-15
spring.redis.database=0
spring.redis.host=192.168.66.130
spring.redis.port=6379
spring.redis.password=123@456
#连接池信息配置.
spring.redis.lettuce.pool.max-active=
spring.redis.lettuce.pool.max-idle=
spring.redis.lettuce.pool.max-wait=
spring.redis.lettuce.pool.min-idle=
spring.redis.lettuce.shutdown-timeout=
#连接池最大连接数
spring.redis.jedis.pool.max-active=8
#连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
#连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0

·Spring Boot·的自动配置类中提供了·RedisAutoConfiguration·进行Redis的配置,部分源码
在这里插入图片描述
由这一段源码可以看到, application.properties中配置的信息将被注入RedisProperties中,如果开发者自己没有提供RedisTemplate或者StringRedis Template实例,则Spring Boot默认会提供这两个实例, RedisTemplateStringRedisTemplate实例则提供了Redis的基本操作方法。
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
public class BookController {
@Autowired
RedisTemplate redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@GetMapping("/test1")
public void test1() {
ValueOperations<String, String> ops1 = stringRedisTemplate.opsForValue();
ops1.set("name", "三国演义");
String name = ops1.get("name");
System.out.println(name);
ValueOperations ops2 = redisTemplate.opsForValue();
Book b1 = new Book();
b1.setId(1);
b1.setName("红楼梦");
b1.setAuthor("曹雪芹");
ops2.set("b1", b1);
Book book = (Book) ops2.get("b1");
System.out.println(book);
}
}
  • StringRedisTemplateRedisTemplate的子类, StringRedisTemplate中的keyvalue都是字符串,采用的序列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化方案是JdkSerializationRedisSerializer.无论是StringRedis Template还是RedisTemplate,操作Redis的方法都是一致的。
  • StringRedisTemplateRedisTemplate都是通过opsForValue, opsForZSet或者opsForSet等方法首先获取一个操作对象,再使用该操作对象完成数据的读写。
  • 第10行向Redis中存储一条记录,第11行将之读取出来,第18行向Redis中存储一个对象,第19行将之读取出来。
6.1.4 Redis集群整合Spring Boot.
1,搭建Redis集群

·(1)集群原理·Redis集群中,所有的Redis节点彼此互联,节点内部使用二进制协议优化传输速度和带宽。当一个节点挂掉后,集群中超过半数的节点检测失效时才认为该节点已失效。不同于Tomcat集群需要使用反向代理服务器, Redis集群中的任意节点都可以直接和Java客户端连接

Redis集群上的数据分配则是采用哈希槽(HASH SLOT) , Redis集群中内置了16384个哈希槽,当有数据需要存储时, Redis会首先使用CRC16算法对key进行计算,将计算获得的结果16384取余,这样每一个key都会对应一个取值在0-16383之间的哈希槽, Redis则根据这个余数将该条数据存储到对应的Redis节点上,开发者可根据每个Redis实例的性能来调整每个Redis实例上哈希槽分布范伟

6.2 整合MongoDB.

6.2.1 MongoDB简介

·MongoDB·是一种面向文档的数据库管理系统,它是一个介于关系型数据库和非关系型数据库,之间的产品, ·MongoDB·功能丰富,它支持一种类似JSONBSON数据格式,既可以存储简单的数据格式,也可以存储复杂的数据类型。·MongoDB·最大的特点是它支持的查询语言非常强大,并且还支持对数据建立索引。总体来说, ·MongoDB·是一款应用相当广泛的NosQL数据库。

6.2.2 MongoDB安装

關於Mongodb的學習《MongoDB大数据处理权威指南》读书笔记:https://blog.csdn.net/sanhewuyang/article/details/107597408

6.2.3 MongoDB整合Spring Boot.

借助于Spring Data MongoDB, Spring BootMongoDB也提供了开箱即用的自动化配置方案,具体配置步骤如下

1
2
3
4
     <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
1
2
3
4
5
6
7
8
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=test
spring.data.mongodb.host=192.168.248.144
spring.data.mongodb.port=27017
spring.data.mongodb.username=root
spring.data.mongodb.password=123
#spring.data.mongodb.uri=mongodb://root:123@192.168.248.144:27017/admin
#spring.data.mongodb.uri=mongodb://192.168.248.144:27017/test
1
2
3
4
5
public interface BookDao extends MongoRepository<Book,Integer> {
List<Book> findByAuthorContains(String author);
Book findByNameEquals(String name);
}

使用MongoTemplate除了继承MongoRepository外,Spring Data MongoDB还提供了MongoTemplate用来方便地操作MongoDB。在Spring Boot中,若添加了MongoDB相关的依赖,而开发者并没有提供MongoTemplate ,则默认会有一个MongoTemplate注册到Spring容器中,相关配置源码在MongoDataAutoConfiguration类中。因此,用户可以直接使用MongoTemplate,在Controller中直接注入MongoTemplate就可以使用了,添加如下代码到第5步的Controller中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class BookController {
@Autowired
BookDao bookDao;
@Autowired
MongoTemplate mongoTemplate;

@GetMapping("/test2")
public void test2() {
List<Book> books = new ArrayList<>();
....
mongoTemplate.insertAll(books);
List<Book> list = mongoTemplate.findAll(Book.class);
System.out.println(list);
Book book = mongoTemplate.findById(3, Book.class);
System.out.println(book);
}

@GetMapping("/test1")
public void test1() {
List<Book> books = new ArrayList<>();
....
bookDao.insert(books);
List<Book> books1 = bookDao.findByAuthorContains("鲁迅");
System.out.println(books1);
Book book = bookDao.findByNameEquals("朝花夕拾");
System.out.println(book);
}
}

6.3 Session共享

正常情况下, HttpSession是通过Servlet容器创建并进行管理的,创建成功之后都是保存在内存中。如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均衡,此时,来自同一用户的HTTP请求就有可能被分发到不同的实例上去,如何保证各个实例之间Session的同步就成为一个必须解决的问题。

Spring Boot提供了自动化的Session共享配置,它结合Redis可以非常方便地解决这个问题。使用Redis解决Session共享问题的原理非常简单,就是把原本存储在不同服务器上Session拿出来放在一个独立的服务器上.
在这里插入图片描述
当一个请求到达Nginx服务器后,首先进行请求分发,假设请求被real serverl处理了, real server在处理请求时,无论是存储Session还是读取Session,都去操作Session服务器而不是操作自身内存中的Session,其他real server在处理请求时也是如此,这样就可以实现Session共享

6.3.1 Session共享配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

除了Redis依赖之外,这里还要提供spring-session-data-redis依赖, Spring Session可以做到透·明化替换掉应用的Session容器。项目创建成功后,在application.properties中进行Redis基本连接信息配置,代码如下:

1
2
3
4
5
6
7
8
spring.redis.database=0
spring.redis.host=192.168.66.130
spring.redis.port=6379
spring.redis.password=123@456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

添加··@EnableRedisHttpSession

1
2
3
4
5
6
7
8
/**
* session托管到redis
*/
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 3600*24, redisFlushMode = RedisFlushMode.ON_SAVE, redisNamespace = "aurora-web")
public class RedisSessionConfig {

}
  • maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的server.session.timeout 属性不再生效。
  • 经过上面的配置后,Session调用就会自动去Redis存取。另外,想要达到Session共享的目的,只需要在其他的系统上做同样的配置即可。
@EnableRedisHttpSession源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {
//Session默认过期时间,单位秒,默认1800秒
int maxInactiveIntervalInSeconds() default 1800;

//配置key的namespace,默认的是spring:session,如果不同的应用共用一个redis,应该为应用配置不同的namespace,这样才能区分这个Session是来自哪个应用的
String redisNamespace() default "spring:session";

//配置刷新Redis中Session方式,默认是ON_SAVE模式,只有当Response提交后才会将Session提交到Redis,也可以配置成IMMEDIATE模式,即所有对Session的更改会立即更新到Redis
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;

//清理过期Session的定时任务
String cleanupCron() default "0 * * * * *";
}

测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class HelloController {
@Value("${server.port}")
String port;
@PostMapping("/save")
public String saveName(String name, HttpSession session) {
session.setAttribute("name", name);
return port;
}
@GetMapping("/get")
public String getName(HttpSession session) {
return port + ":"
+ session.getAttribute("name").toString();
}
}
6.3.2 Nginx负载均衡

关于Nginx,可以访问Nginx 学习笔记(《深入理解Nginx:模块开发与架构解析》读书笔记):https://blog.csdn.net/sanhewuyang/article/details/114488191
nginx.conf·配置文件修改:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
server {
location / {
proxy_pass http: //backend;
}
}

第7章构建RESTful服务

7.1 REST简介

REST (Representational State Transfer)是一种Web软件架构风格,它是一种风格,而不是标准,匹配或兼容这种架构风格的网络服务称为REST服务。REST服务简洁并且有层次, REST通常基于HTTP,URIXML以及HTML这些现有的广泛流行的协议和标准。

在REST中,资源是由URI来指定的,对资源的增删改查操作可以通过HTTP协议提供的GET, POST, PUT, DELETE等方法实现。使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信会话状态由客户端来维护,这可以让不同的服务器处理一系列请求中的不同请求,进而提高服务器的扩展性。在前后端分离项目中,一个设计良好的Web软件架构必然要满足REST风格。在Spring MVC框架中,开发者可以通过@RestController注解开发一个RESTful服务,不过,Spring Boot对此提供了自动化配置方案,开发者只需要添加相关依赖就能快速构建一个RESTful服务。

7.2 JPA实现REST

Spring Boot 中,使用Spring Data JPASpring Data Rest可以快速开发出一个RESTful应用。接下来向读者介绍Spring Boot中非常方便的RESTful应用开发

7.2.1 基本实现

这里的依赖除了数据库相关的依赖外,还有Spring Data JPA的依赖以及Spring Data Rest的依赖。项目创建完成后,在application.properties 中配置基本的数据库连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

这里的依赖除了数据库相关的依赖外,还有Spring Data JPA的依赖以及Spring Data Rest的依赖。项目创建完成后,在application.properties中配置基本的数据库连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///jparestful
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database=mysql
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.show-sql=true

##每页默认记录数,缺省值为20
#spring.data.rest.default-page-size=2
##分页查询页码参数名,缺省值为page
#spring.data.rest.page-param-name=page
##分页查询记录数参数名,缺省值为size
#spring.data.rest.limit-param-name=size
##分页查询排序参数名,缺省值为sort
#spring.data.rest.sort-param-name=sort
##base-path表示给所有请求路径都加上前缀
#spring.data.rest.base-path=/api
##添加成功时是否返回添加内容
#spring.data.rest.return-body-on-create=true
##更新成功时是否返回更新内容
#spring.data.rest.return-body-on-update=true

创建实体类,创建BookRepository类继承JpaRepository, JpaRepository中默认提供了一些基本的操作方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();

List<T> findAll(Sort var1);

List<T> findAllById(Iterable<ID> var1);

<S extends T> List<S> saveAll(Iterable<S> var1);

void flush();

<S extends T> S saveAndFlush(S var1);

<S extends T> List<S> saveAllAndFlush(Iterable<S> var1);

/** @deprecated */
@Deprecated
default void deleteInBatch(Iterable<T> entities) {
this.deleteAllInBatch(entities);
}

void deleteAllInBatch(Iterable<T> var1);

void deleteAllByIdInBatch(Iterable<ID> var1);

void deleteAllInBatch();

/** @deprecated */
@Deprecated
T getOne(ID var1);

T getById(ID var1);

<S extends T> List<S> findAll(Example<S> var1);

<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

7.2.2 自定义请求路径

默认情况下,请求路径都是实体类名小写加s,如果开发者想对请求路径进行重定义,通过@RepositoryRestResource注解即可实现,下面的案例只需在BookRepository上添加@RepositoryRestResource注解即可:

1
2
3
4
5
@CrossOrigin
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
.....
}
  • @RepositoryRestResource·注解的·path·属性表示将所有请求路径中的books都修改为bs,http://ocalhost: 8080/bs:
  • collectionResourceRel属性表示将返回的JSON集合中book集合的key修改为bs;
  • itemResourceRel表示将返回的JSON集合中的单个book的key修改为b,

7.2.3 自定义查询方法.

默认的查询方法支持分页查询排序查询以及按照id查询,如果开发者想要按照某个属性查询,只需在BookRepository中定义相关方法并暴露出去即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
@Override
@RestResource(exported = false)
void deleteById(Integer integer);

@RestResource(path = "author",rel = "author")
List<Book> findByAuthorContains(@Param("author") String author);
@RestResource(path = "name",rel = "name")
Book findByNameEquals(@Param("name") String name);
}
  • ·自定义查询只需要在BookRepository中定义相关查询方法即可,方法定义好之后可以不添加@RestResource注解,默认路径就是方法名。以第4行定义的方法为例,若不添加@RestResource注解, 则默 认 该方法的调用路径为http://ocalhost:8080/bs/search/indByAuthorContains?author=鲁迅。如果想对查询路径进行自定义,只需要添加@RestResource注解即可, path属性即表示最新的路径。还是以第4行的方法为例,添加@RestResource(path = "author",rel = "author")注解后的查询路径为”http://ocalhost:8080/bs/search/author?author=鲁迅".
  • 用户可以直接访问http/ocalhost:8080/bs/search路径查看该实体类暴露出来了哪些查询方法,默认情况下,在查询方法展示时使用的路径是方法名,通过@RestResource注解中的rel属性可以对这里的路径进行重定义,如图7-6所示。

7.2.4 隐藏方法

默认情况下,凡是继承了Repository接口(或者Repository的子类)的类都会被暴露出来,即开发者可执行基本的增删改查方法。以上文的BookRepository为例,如果开发者提供了BookRepository继承自Repository,就能执行对Book的基本操作,如果开发者继承了Repository但是又不想暴露相关操作,做如下配置即可:

1
2
3
4
5
6
7
//@RepositoryRestResource(exported = false)
public interface BookRepository extends JpaRepository<Book, Integer> {
@Override
@RestResource(exported = false)
void deleteById(Integer integer);
....
}

将·@RepositoryRestResource注解中的exported属性置为false之后,则增删改查接口都会失效, BookRepository类中定义的相关方法也会失效。若只是单纯地不想暴露某个方法,则在方法上进行配置即可,例如开发者想屏蔽DELETE接口.

7.2.5 配置CORS

4.6节已经向读者介绍了CORS两种不同的配置方式,一种是直接在方法上添加@CrosSorigin注解,另一种是全局配置全局配置在这里依然适用,但是默认的RESTful工程不需要开发者自己提供Controller,因此添加在Controller的方法上的注解可以直接写在BookRepository上,代码如下:接口跨域:@CrossOrigin注解添加到某一个方法上即可。

1
2
3
4
5
6
7
//@CrossOrigin
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
@CrossOrigin
List<Book> findByAuthorContains(@Param("author") String author);
....
}

7.2.6其他配置

application.properties配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
##每页默认记录数,缺省值为20
#spring.data.rest.default-page-size=2
##分页查询页码参数名,缺省值为page
#spring.data.rest.page-param-name=page
##分页查询记录数参数名,缺省值为size
#spring.data.rest.limit-param-name=size
##分页查询排序参数名,缺省值为sort
#spring.data.rest.sort-param-name=sort
##base-path表示给所有请求路径都加上前缀
#spring.data.rest.base-path=/api
##添加成功时是否返回添加内容
#spring.data.rest.return-body-on-create=true
##更新成功时是否返回更新内容
#spring.data.rest.return-body-on-update=true

当然,这些XML配置也可以在Java代码中配置,且代码中配置的优先级高于application.properties配置的优先级,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultPageSize(2)
.setPageParamName("page")
.setLimitParamName("size")
.setSortParamName("sort")
.setBasePath("/api")
.setReturnBodyOnCreate(true)
.setReturnBodyOnUpdate(true);
}
}

7.2.7 JPA使用rest自定义链接

1
2
3
4
5
6
7
8
@Configuration
public class MyResourceProcessor implements ResourceProcessor {
@Override
public ResourceSupport process(ResourceSupport resourceSupport) {
resourceSupport.add(new Link("http://www.baidu.com", "百度一下"));
return resourceSupport;
}
}

7.3 MongoDB实现REST

MongoDB整合Spring Boot,而使用Spring Boot快速构建RESTful·服务除了结合Spring Data JPA之外,也可以结合Spring Data MongoDB实现。使用Spring DataMongoDB构建RESTful服务也是三个步骤,分别如下。

1
2
3
4
5
6
7
8
     <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

这里Spring Data Rest的依赖和7.2节中的一致,只是将Spring Data JPA的依赖变为Spring DataMongoDB的依赖。项目创建成功后,在application.properties中配置MongoDB的基本连接信息,

1
2
3
4
5
6
spring.data.mongodb.authentication-database=test
spring.data.mongodb.database=test
spring.data.mongodb.username=sang
spring.data.mongodb.password=123
spring.data.mongodb.host=192.168.248.144
spring.data.mongodb.port=27017
1
2
public interface BookRepository extends MongoRepository<Book,Integer> {
}

第8章开发者工具与单元测试

8.1 devtools简介

Spring Boot中提供了一组开发工具spring-boot-devtools,可以提高开发者的工作效率,开发者可以将该模块包含在任何项目中, spring-boot-devtools最方便的地方莫过于热部署了。

8.2 devtools实战

8.2.1 基本用法

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
  • 这里多了一个optional选项,是为了防止将devtools依赖传递到其他模块中。当开发者将应用打包运行后, devtools会被自动禁用。
  • 当开发者将spring-boot-devtools引入项目后,只要classpath路径下的文件发生了变化,项目就会自动重启,这极大地提高了项目的开发速度。

8.2.2 基本原理

Spring Boot中使用的自动重启技术涉及两个类加载器,一个是baseclassloader,用来加载不会变化的类,例如项目引用的第三方的jar;另一个是restartclassloader,用来加载开发者自己写的会·变化的类。当项目需要重启时, restartclassloader将被一个新创建的类加载器代替,而baseclassloader则继续使用原来的,这种启动方式要比冷启动快很多,因为baseclassloader已经存在并且已经加载好

8.3单元测试

8.3.1 基本用法

当开发者使用Intelli IDEA或者在线创建一个Spring Boot项目时,创建成功后,默认都添加了spring-bool-starter-est依赖,并且创建好了测试类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@RunWith(SpringRunner.class)
@SpringBootTest
public class AppTest {
MockMvc mockMvc;
@Autowired
WebApplicationContext wac;
@Autowired
HelloService helloService;
@Before
public void before() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void test2() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/hello", "name=aaa")).andDo(MockMvcResultHandlers.print());
}
@Test
public void test1() {
System.out.println(helloService.sayHello("里斯"));
}
}
  • @RunWith注解,该注解将JUnit执行类修改为SpringRunner,而SpringRunnerSpring Framework中测试类SpringJUnit4ClassRunner的别名。
  • @Spring BootTest注解除了提供Spring TestContext中的常规测试功能之外,还提供了其他特性:提供默认的ContextLoader, 自动搜索@Spring BootConfiguration、自定义环境属性、为不同的webEnvironment模式提供支持,这里的webEnvironment模式主要有4种.這裏不説了。

8.3.2 Service测试

1
2
3
4
5
6
@Service
public class HelloService {
public String sayHello(String name) {
return "Hello " + name + " !";
}
}
1
2
3
4
5
6
7
@Autowired
HelloService helloService;
@Test
public void contextLoads() {
String hello = helloService.sayHello("Michael");
Assert.assertThat(hello, Matchers.is("Hello Michael !"));
}

8.3.3 Controller测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test2() throws Exception {
ObjectMapper om = new ObjectMapper();
Book book = new Book();
book.setAuthor("罗贯中");
book.setName("三国演义");
book.setId(1);
String s = om.writeValueAsString(book);
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders
.post("/book")
.contentType(MediaType.APPLICATION_JSON)
.content(s))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
System.out.println(mvcResult.getResponse().getContentAsString());
}
1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(String name) {
return helloService.sayHello(name);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MockMvc mockMvc;
@Autowired
WebApplicationContext wac;
@Autowired
HelloService helloService;
@Before
public void before() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void test1() throws Exception {
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders
.get("/hello")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("name", "Michael"))
//返回什么数据
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
System.out.println(mvcResult.getResponse().getContentAsString());
}

除了MockMvc这种测试方式之外, Spring Boot还专门提供了TestRestTemplate用来实现集成测试,若开发者使用了@Spring BootTest注解,则TestRestTemplate将自动可用,直接在测试类中注入即可。注意,如果要使用TestRestTemplate进行测试,需要将@Spring BootTest注解中webEnvironment属性的默认值由WebEnvironment.MOCK修改为webEnvironment.DEFINED PORT或者WebEnvironment.RANDOM PORT,因为这两种都是使用一个真实的Servlet环境而不是模拟的Serlet环境。其代码如下:

1
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
1
2
3
4
5
6
7
@Autowired
TestRestTemplate restTemplate;
@Test
public void test3() {
ResponseEntity<String> hello = restTemplate.getForEntity("/hello?name={0}", String.class, "Michael");
System.out.println(hello.getBody());
}
  • test2方法演示了POST请求如何传递JSON数据,首先在32行将一个book对象转为一段JSON,然后在36行设置请求的contentType为APPLICATION-JSON,最后在37行设置content为上传的JSON即可。

8.3.4 JSON测试

开发者可以使用@JsonTest测试JSON序列化反序列化是否工作正常,该注解将自动配置Jackson ObjectMapper.@JsonComponent以及Jackson Modules.如果开发者使用Gson代替Jackson,该注解将配置Gson,具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RunWith(SpringRunner.class)
@JsonTest
public class JSONTest {
@Autowired
JacksonTester<Book> jacksonTester;
@Test
public void testSerialize() throws IOException {
Book book = new Book();
book.setId(1);
book.setName("三国演义");
book.setAuthor("罗贯中");
Assertions.assertThat(jacksonTester.write(book))
.isEqualToJson("book.json");
Assertions.assertThat(jacksonTester.write(book))
.hasJsonPathStringValue("@.name");
Assertions.assertThat(jacksonTester.write(book))
.extractingJsonPathStringValue("@.name")
.isEqualTo("三国演义");
}
@Test
public void testDeserialize() throws Exception {
String content = "{\"id\":1,\"name\":\"三国演义\",\"author\":\"罗贯中\"}";
// Book book = new Book();
// book.setId(1);
// book.setName("三国演义");
// book.setAuthor("罗贯中");
Assertions.assertThat(jacksonTester.parseObject(content).getName())
.isEqualTo("三国演义");
}
}

第9章Spring Boot缓存

Spring 3.1中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接,从缓存中获取,否则再去执行该方法。但是, Spring中并未提供缓存的实现,而是提供了一套缓存API,开发者可以自由选择缓存的实现, 目前Spring Boot支持的缓存有如下几种:Cache (JSR-107)EhCache 2.x HazelcastInfinispanCouchbase RedisCaffeineSimple

9.1 Ehcache 2.x缓存.

Ehcache缓存在Java开发领域已是久负盛名,在Spring Boot中,只需要一个配置文件就可以将Ehcache集成到项目中。Ehcache 2.x的使用步骤如下。

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

2.添加缓存配置文件如果Ehcache的依赖存在,并且在classpath下有一个名为ehcache2.xmlEhcache配置文件,那么EhCacheCacheManager将会自动作为缓存的实现。因此,在resources目录下创建ehcache.xml文件作为Ehcache缓存的配置文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="book_cache"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="10"/>
</ehcache>

这是一个常规的Ehcache配置文件,提供了两个缓存策略,一个是默认的,另一个名为book-cache.其中,

  • name表示缓存名称; maxElementsInMemory表示缓存最大个数:
  • eternal表示缓存对象是否永久有效,一旦设置了永久有效, timeout将不起作用;
  • timeToldleSeconds表示缓存对象在失效前的允许闲置时间(单位:秒) ,当eternal-false对象不是永久有效时,该属性才生效;
  • timeToLiveSeconds表示缓存对象在失效前允许存活的时间(单位:秒),当eternal-false对象不是永久有效时,该属性才生效;
  • overflowToDisk表示当内存中的对象数量达到maxElementsInMemory时, Ehcache是否将对象写到磁盘中;
  • diskExpiryThreadIntervalSeconds表示磁盘失效线程运行时间间隔。

另外,如果开发者想自定义Ehcache配置文件的名称和位置,可以在application.properties中添加如下配置:

1
spring.cache.ehcache.config=classpath:ehcache2.xml
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableCaching
public class CacheApplication {

public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
@Autowired
MyKeyGenerator myKeyGenerator;
@Cacheable(keyGenerator = "myKeyGenerator")
public Book getBookById(Integer id) {
System.out.println("getBookById");
Book book = new Book();
book.setId(id);
book.setName("三国演义");
book.setAuthor("罗贯中");
return book;
}
@CachePut(key = "#book.id")
public Book updateBookById(Book book) {
System.out.println("updateBookById");
book.setName("三国演义2");
return book;
}
@CacheEvict(key = "#id")
public void deleteBookById(Integer id) {
System.out.println("deleteBookById");
}
}

9.2 Redis单机缓存

9.3 Redis集群缓存

9.3.1 搭建Redis集群.

9.3.2 配置缓存

9.3.3 使用缓存

第10章 Spring Boot安全管理…

发布于

2021-06-18

更新于

2023-06-21

许可协议

评论
加载中,最新评论有1分钟缓存...
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×